#include "host_framework.h"

#define PROGRAM_FILE "add_numbers.cl"
#define KERNEL_FUNC "add_numbers"
#define ARRAY_SIZE 64

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

void cl_destroy(cl_host_ctx_t *ctx) {
    clReleaseCommandQueue(ctx->cmd_queue);
    clReleaseContext(ctx->platform.context);
    free(ctx);
}

void cl_set_userdata(cl_host_ctx_t *ctx, void *userdata) {
    ctx->data = userdata;
}

void *cl_userdata(cl_host_ctx_t *ctx) { return ctx->data; }

cl_host_ctx_t *cl_initialize(const char *platform_name, const char *dev_name) {
    cl_platform_id platform[10];
    cl_device_id dev[10];
    int err;

    cl_host_ctx_t *ctx;
    ctx = calloc(1, sizeof(cl_host_ctx_t));
    if (!ctx) {
        return NULL;
    }

    /* Identify a platform */
    cl_uint num_available;
    err = clGetPlatformIDs(CL_HOST_NUM_PLATFORM_MAX, platform, &num_available);
    if (err != CL_SUCCESS) {
        goto error;
    }

    for (cl_uint i = 0; i < num_available; ++i) {
        char pname[512];
        size_t size_ret;
        err = clGetPlatformInfo(platform[i], CL_PLATFORM_NAME, sizeof(pname),
                                pname, &size_ret);
        if (err != CL_SUCCESS) {
            goto error;
        }
        if (!strcmp(pname, platform_name)) {
            ctx->platform.id = platform[i];
            break;
        }
    }

    /* Access a device */
    err = clGetDeviceIDs(ctx->platform.id, CL_DEVICE_TYPE_ALL,
                         CL_HOST_NUM_DEVICE_MAX, dev, &num_available);
    if (err != CL_SUCCESS) {
        goto error;
    }
    for (cl_uint i = 0; i < num_available; ++i) {
        char pname[512];
        size_t size_ret;
        err = clGetDeviceInfo(dev[i], CL_DEVICE_NAME, sizeof(pname), pname,
                              &size_ret);
        if (err != CL_SUCCESS) {
            goto error;
        }

        if (!strcmp(pname, dev_name)) {
            ctx->platform.device.id = dev[i];
            break;
        }
    }

    cl_int errcode_ret;
    ctx->platform.context = clCreateContext(NULL, 1, &ctx->platform.device.id,
                                            NULL, NULL, &errcode_ret);
    if (errcode_ret != CL_SUCCESS) {
        goto error;
    }

    ctx->cmd_queue = clCreateCommandQueue(
        ctx->platform.context, ctx->platform.device.id, 0, &errcode_ret);
    if (errcode_ret != CL_SUCCESS) {
        goto error;
    }

    ctx->cmd_queue = clCreateCommandQueue(
        ctx->platform.context, ctx->platform.device.id, 0, &errcode_ret);
    if (errcode_ret != CL_SUCCESS) {
        goto error;
    }

    return ctx;

error:
    cl_destroy(ctx);
    return NULL;
}

#include <dlfcn.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

typedef void (*cl_plugin_init_func_t)();
typedef void (*cl_plugin_deinit_func_t)();
typedef const char *(*cl_plugin_get_source_name_func_t)();
typedef const char *(*cl_plugin_get_kernel_name_func_t)();
typedef int (*cl_plugin_do_work_func_t)(cl_host_ctx_t *ctx);

int cl_load_kernel(cl_host_ctx_t *ctx, const char *module_name) {
    char path[512];
    snprintf(path, sizeof(path), "kernels/%s/%s.so", module_name, module_name);
    ctx->so_handle = dlopen(path, RTLD_LAZY);
    if (!ctx->so_handle) {
        fprintf(stderr, "Failed to open so lib: %s\n", path);
        fprintf(stderr, "%s\n", dlerror());
        return -1;
    }

    cl_plugin_init_func_t init_func = dlsym(ctx->so_handle, "cl_plugin_init");
    if (!init_func) {
        fprintf(stderr, "%s\n", "Failed to find plugin init func");
        return -1;
    }
    (*init_func)();

    cl_plugin_get_source_name_func_t get_src_name_func;
    get_src_name_func = dlsym(ctx->so_handle, "cl_plugin_get_source_name");
    if (!get_src_name_func) {
        fprintf(stderr, "%s\n",
                "Failed to find plugin cl_plugin_get_source_name func");
        return -1;
    }
    const char *fname;
    fname = (*get_src_name_func)();

    struct stat stat;
    int fd;
    snprintf(path, sizeof(path), "kernels/%s/%s", module_name, fname);
    fprintf(stderr, "OpenCL C source file: %s\n", path);
    fd = open(path, O_RDONLY);
    if (fd < 0) {
        fprintf(stderr, "Failed to open OpenCL C source file\n");
        return -1;
    }
    if (fstat(fd, &stat) < 0) {
        close(fd);
        fprintf(stderr, "Failed to get OpenCL C source file size\n");
        return -1;
    }
    size_t len = stat.st_size;
    char *source = malloc(len);
    if (!source) {
        fprintf(stderr, "OOM\n");
        close(fd);
        return -1;
    }
    if (read(fd, source, len) != (ssize_t)len) {
        close(fd);
        fprintf(stderr, "Failed to read OpenCL C source file\n");
        return -1;
    }
    close(fd);

    cl_int errcode_ret;
    ctx->program = clCreateProgramWithSource(
        ctx->platform.context, 1, (const char **)&source, &len, &errcode_ret);
    if (errcode_ret != CL_SUCCESS) {
        fprintf(stderr, "Failed to create cl program\n");
        return -1;
    }

    errcode_ret = clBuildProgram(ctx->program, 1, &ctx->platform.device.id,
                                 NULL, NULL, NULL);
    if (errcode_ret != CL_SUCCESS) {
        fprintf(stderr, "Failed to build cl program\n");
        return -1;
    }

    cl_plugin_get_kernel_name_func_t get_kernel_func;
    get_kernel_func = dlsym(ctx->so_handle, "cl_plugin_get_kernel_name");
    if (!get_kernel_func) {
        fprintf(stderr, "%s\n",
                "Failed to find plugin cl_plugin_get_kernel_name func");
        return -1;
    }
    const char *kernel_name;
    kernel_name = (*get_kernel_func)();
    fprintf(stderr, "kernel_name: %s\n", kernel_name);

    ctx->kernel = clCreateKernel(ctx->program, kernel_name, &errcode_ret);
    if (errcode_ret != CL_SUCCESS) {
        fprintf(stderr, "Failed to create cl kernel\n");
        return -1;
    }

    return 0;
}

int cl_do_work(cl_host_ctx_t *ctx) {
    cl_plugin_do_work_func_t func;
    func = dlsym(ctx->so_handle, "cl_plugin_do_work");
    if (!func) {
        fprintf(stderr, "Failed to find cl_plugin_do_work \n");
        return -1;
    }

    return (*func)(ctx);
}

int main() {
    cl_host_ctx_t *ctx;
    ctx = cl_initialize("ARM Platform", "Mali-T860");
    if (!ctx) {
        fprintf(stderr, "%s\n", "Failed to create opencl host context");
    }

    if (cl_load_kernel(ctx, "add_number") < 0) {
        fprintf(stderr, "%s\n", "Failed to load add_number");
        return -1;
    }

    if(cl_do_work(ctx) < 0) {
        fprintf(stderr, "%s\n", "Failed to run");
        return -1;
    }

    cl_destroy(ctx);

    return 0;
}
